//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <mem/heap.h>
#include <mem/page.h>
#include <kernio/io.h>
#include <process/common.h>

// Functions

// whether or not to garbage collect
#define DO_GARBAGE_COLLECTION 0

// offset from himem of a page table
#define PT_OFF(v) ((v>>22)<<12)

// kills a thread
void kill( uint32_t pid )
{
	// check for a valid pid
	if( ((int32_t) pid) == -1 )
		return;

	// firstly check if it's the current process
	if( pid == currprocess->id )
	{
		// kill interrupts so we don't have our now freed mapping getting fried
		asm volatile( "cli" );

		// kill all children
		uint32_t c;
		for( c = 0; c < currprocess->numchildren; c++ )
			kill( currprocess->childpids[c] );

		// clean up, but don't do GC
		struct ctx_mem_map* region;
		for(region = currprocess->memmap; region != 0;)
		{
			if(region->type > MEMMAP_ELEM_MAX)
			{
				dprintf("[MEM  ] kill(): invalid region %d, breaking [%x]\n", region->type, region);
				break;
			}

			dprintf("[MEM  ] kill(): freeing region of type %d\n", region->type);
			size_t npages = region->size / 0x1000;
			if(npages == 0) // handle cases where size < 0x1000
				npages = 1;
			size_t n;
			for(n = 0; n < npages; n++)
				kFreePages((uint32_t*) (GetPhysicalAddress(0xfffff000, region->base + (n*0x1000))));
			struct ctx_mem_map* old = region;
			region = region->next;
			free(old);
		}

#if DO_GARBAGE_COLLECTION

		// clean up the address space
		if( currprocess->cr3 != GetKernelDirectory() )
		{
			// traverse the page directory
			unsigned int* pdir = (unsigned int*) 0xfffff000;
			int i, j;
			for( i = 0; i < 1024; i++ )
			{
				// get the page table for this entry
				unsigned int* ptab = (unsigned int*) ((0xFFC00000 + (i*0x1000)) & ~0xFFF);

				// check that the flags are set
				if( !((pdir[i] & 0xFFF) & PF_PRESENT) )
					continue;

				// go through each entry in it
				for( j = 0; j < 1024; j++ )
				{
					// get the physical address for this page
					uint32_t phys = ptab[j] & ~0xFFF;

					// check that the physical page/flags isn't null
					if( phys == 0 || (ptab[j] & 0xFFF) == 0 )
						continue;
					if((ptab[j] & 0xFFF) & PF_KERNELPAGE)
						continue;

					// all is well, so decrement the reference count and keep going
					kDecrRefCount( phys );
					if( kGetRefCount( phys ) == 0 )
					{
						kFreePages( (uint32_t*) phys );
					}
				}

				// destroy the page table
				/*uint32_t pdir_phys = pdir[i] & ~0xFFF;
				if((pdir[i] & 0xFFF) & PF_KERNELPAGE)
					continue;
				kDecrRefCount( pdir_phys );
				if( kGetRefCount( pdir_phys ) == 0 )
				{
					kFreePages( (uint32_t*) pdir_phys );
				}*/
			}

			// free the page directory, if we can
			kDecrRefCount( currprocess->cr3 );
			if( kGetRefCount( currprocess->cr3 ) == 0 )
			{
				kFreePages( (uint32_t*) currprocess->cr3 );
			}
		}
		else
		{
			// free the arguments
			free( (char*) currprocess->argv );
		}
#endif

		// check if we need to return the argv
		if(currprocess->cr3 == GetKernelDirectory())
			free( (char*) currprocess->argv );

		// return the stack
		ReturnChildKernelStack(currprocess->kernstack);
		if(currprocess->flags & TF_USPACE)
			ReturnChildUserStack(currprocess, currprocess->userstack);

		// kill the current process and reschedule
		currprocess->status = PSTATE_DEAD; // for waiting processes

		// take this process OFF wherever it is now
		struct pentry* ent = currprocess->ent;
		if(ent->next)
			ent->next->prev = ent->prev;
		if(ent->prev)
			ent->prev->next = ent->next;

		// one less child
		currprocess->parent->numchildren--;
		if(((int32_t) currprocess->parent->numchildren) <= 0)
		{
			// unlink, and free the parent
			ent = currprocess->parent->ent;
			if(ent->next)
				ent->next->prev = ent->prev;
			if(ent->prev)
				ent->prev->next = ent->next;
			free(ent->me->user_stack_bitmap);
			free(ent->me);
			free(ent);
		}

		// now the process is unlinked, we can link it into the dead queue
		if(p_deadq == 0)
		{
			dprintf("adding to dead queue\n");
			p_deadq = ent;
			ent->next = ent->prev = 0;
		}
		else
		{
			// push to the front of the queue
			dprintf("p_deadq->next = %x\n", p_deadq->next);
			p_deadq->prev = ent;
			ent->next = p_deadq;
			ent->prev = 0;
			p_deadq = ent;
		}

		// we're dead now
		if(currprocess->wake_on_die)
			wake(currprocess->wake_on_die);
		
		// spin until the entry is destroyed
		while( 1 ) kSchedule( 0 );
	}
	else
	{
		// we'll need to search the list
		struct pentry* ent;
		for( ent = ptable; ent != 0; ent = ent->next )
		{
			if( pid == ent->me->id )
			{
				ent->me->status = PSTATE_DEAD;
				return;
			}
		}
	}
}
